#include "StdAfx.h"

#include "MainWnd.h"
#include "resource.h"

using boost::wformat;

const UINT UM_CREATEDEVICE = WM_APP + 1;

extern LPCTSTR MAINWND_CLASS_NAME = L"{7AB0814E-3046-4d46-9772-788776CF4EE6}";

LRESULT CALLBACK MainWnd_InitialWindowProc(HWND hWnd, UINT uMsg,
	WPARAM wParam, LPARAM lParam){

	if(uMsg == WM_CREATE){
		LPCREATESTRUCT lpCreate = reinterpret_cast<LPCREATESTRUCT>(lParam);
		assert(lpCreate != NULL);
		if(lpCreate == NULL)
			return (LRESULT) -1L;

		MainWnd *lpMainWnd =
			reinterpret_cast<MainWnd*>(lpCreate->lpCreateParams);
		SetWindowLong(hWnd, GWL_USERDATA, (LONG)lpMainWnd);
		SetWindowLong(hWnd, GWL_WNDPROC,
			reinterpret_cast<LONG>(MainWnd_WindowProc));

		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	else
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

LRESULT CALLBACK MainWnd_WindowProc(HWND hWnd, UINT uMsg,
	WPARAM wParam, LPARAM lParam){

	MainWnd *window = reinterpret_cast<MainWnd*>(
		GetWindowLong(hWnd, GWL_USERDATA));

	return window->WindowProc(hWnd, uMsg, wParam, lParam);
}

ATOM MainWnd::Register(HINSTANCE hInstance){
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(wcex);
	wcex.cbClsExtra = wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.lpfnWndProc = MainWnd_InitialWindowProc;
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = MainWnd_InitialWindowProc;
	wcex.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
	wcex.hCursor = (HCURSOR) LoadCursor(NULL, IDC_ARROW);
	wcex.hIcon = (HICON) LoadImage(hInstance, MAKEINTRESOURCE(IDI_MAINWND),
		IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON),
		LR_SHARED | LR_COLOR);
	wcex.hIconSm = (HICON) LoadImage(hInstance, MAKEINTRESOURCE(IDI_MAINWND),
		IMAGE_ICON, GetSystemMetrics(SM_CXSMICON),
		GetSystemMetrics(SM_CYSMICON), LR_SHARED | LR_COLOR);
	wcex.lpszClassName = MAINWND_CLASS_NAME;
	wcex.lpszMenuName = NULL;

	return TWS(RegisterClassEx(&wcex));
}

const float MainWnd::NEAR_PLANE = -10000.0f;
const float MainWnd::FAR_PLANE = 10000.0f;

MainWnd::MainWnd() : scale(1.0f), hwnd(NULL), pD3D9(NULL) {

}

LRESULT MainWnd::WindowProc(HWND hWnd, UINT uMsg, WPARAM
							wParam, LPARAM lParam){

	switch(uMsg){
		case UM_CREATEDEVICE:
			return OnCreateDevice(hWnd);
			
		HANDLE_MSG(hWnd, WM_PAINT, OnPaint);
		HANDLE_MSG(hWnd, WM_SIZE, OnSize);
		HANDLE_MSG(hWnd, WM_MOUSEWHEEL, OnMouseWheel);
		HANDLE_MSG(hWnd, WM_DESTROY, OnDestroy);
	}
	return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

void MainWnd::OnPaint(HWND hWnd){

	if(device == NULL)
		return;

	// structure for triangle vertex positions
#define VTX(x_, y_) \
	{ x_, y_, 0.0f }
	const struct MYVERTEX
	{
		float m_pos[3]; // x, y, z, 1/w
	}
	triangle[] =
	{
		VTX(-20.0f, -20.0f),
		VTX(0.0f, 20.0f),
		VTX(20.0f, -20.0f),
		VTX(-20.0f, -20.0f)
	};
#undef VTX

	// start the scene and clear the frame buffer
	THR(device->BeginScene());
	THR(device->Clear(0, NULL, D3DCLEAR_TARGET, 0, 1.0f, 0));

	THR(device->SetTransform(D3DTS_PROJECTION, &projectionMx));

	D3DXMATRIX& vm = projectionMx;

	// describe triange vertex data to the device
	THR(device->SetFVF(D3DFVF_XYZ));
	THR(device->DrawPrimitiveUP(D3DPT_LINESTRIP, 3,
		triangle, sizeof(MYVERTEX)));

	// end 3D scene
	THR(device->EndScene());

	// present the scene & handle lost devices
	HRESULT hr = device->Present(NULL, NULL, NULL, NULL);
	while (D3DERR_DEVICELOST == hr)
	{
		do
		{
			::Sleep(1000);
			hr = device->TestCooperativeLevel();
		}
		while (hr != D3DERR_DEVICENOTRESET);

		if (FAILED(device->Reset(&present)))
		{
			hr = D3DERR_DEVICELOST;
		}
	}

	// clear Win32 dirty region
	TWS(::ValidateRect(hWnd, NULL));
}

BOOL MainWnd::OnCreateDevice(HWND hwnd){
	CComPtr<IDirect3D9> d3d;
	pD3D9 = Direct3DCreate9(D3D_SDK_VERSION);

	if(pD3D9 == NULL){
		OutputDebugString(L"Direct3DCreate9 failed\n");
		return FALSE;
	}

	d3d.Attach(pD3D9);

	if(FAILED(d3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &mode))){
		OutputDebugString(L"GetAdapterDisplayMode failed\n");
		return FALSE;
	}

	InitPresentationParameters();

	if(FAILED(d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
		hwnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE,
		&present, &device))){
		OutputDebugString(L"CreateDevice failed\n");
		return FALSE;
	}

	device->SetRenderState(D3DRS_LIGHTING, FALSE);

	UpdateProjection();

	D3DXMATRIX viewMx;
	D3DXVECTOR3 eye(0.0f, 0.0f, -2000.0f), target(0.0f, 0.0f, 0.0f),
		up(0.0f, 1.0f, 0.0f);
	D3DXMatrixLookAtLH(&viewMx, &eye, &target, &up);
	THR(device->SetTransform(D3DTS_VIEW, &viewMx));

	return TRUE;
}

void MainWnd::UpdateProjection(){
	D3DXMatrixOrthoLH(&projectionMx, present.BackBufferWidth / scale,
		present.BackBufferHeight / scale, NEAR_PLANE, FAR_PLANE);

	//OutputDebugString(str(wformat(L"Updating... %f %f %f\n") %
	//	present.BackBufferWidth % present.BackBufferHeight %
	//	scale).c_str());

}

void MainWnd::OnDestroy(HWND hWnd){
	PostQuitMessage(0);
}

void MainWnd::OnSize(HWND hwnd, UINT state, int cx, int cy){
	if(device){
		InitPresentationParameters();
		device->Reset(&present);
	}

	FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc);
}

void MainWnd::InitPresentationParameters(){

	ZeroMemory(&present, sizeof(present));

	RECT rect;

	TWS(GetClientRect(this->hwnd, &rect));
	present.BackBufferWidth = rect.right - rect.left;
	present.BackBufferHeight = rect.bottom - rect.top;
	present.BackBufferFormat = mode.Format;
//	present.BackBufferCount = 1;
//	present.MultiSampleType = D3DMULTISAMPLE_NONE;
//	present.MultiSampleQuality = 0;
	present.SwapEffect = D3DSWAPEFFECT_FLIP;
	present.hDeviceWindow = hwnd;
	present.Windowed = TRUE;
//	present.EnableAutoDepthStencil = FALSE;
//	present.AutoDepthStencilFormat = D3DFMT_UNKNOWN;

}

void MainWnd::SetHWnd(HWND hWnd){
	this->hwnd = hWnd;
}

void MainWnd::OnMouseWheel(HWND hwnd, int xPos, int yPos,
	int zDelta, UINT fwKeys){
	
	if(zDelta > 0)
		scale = scale * 1.1f;
	else
		scale = scale / 1.1f;

	UpdateProjection();

	InvalidateRect(hwnd, NULL, FALSE);
}
